package openapi

import (
	"crypto/x509"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"gitee.com/bjf-fhe/apinx/errors"

	"github.com/getkin/kin-openapi/openapi3"
)

// type ContentTestFn func(def *openapi3.Schema, value interface{}) (bool, interface{}, error)

func GetBaseUrlFromServer(serv *openapi3.Server, args ...map[string]string) string {
	baseURL := serv.URL
	if len(args) > 0 {
		for k, v := range args[0] {
			matcher := regexp.MustCompile("{\\s*" + k + "\\s*}")
			baseURL = matcher.ReplaceAllString(baseURL, v)
		}
	}
	return baseURL
}

type ActionTypeWhenNotDefined byte

const (
	//当未定义时，返回错误，当返回整体未定义时，作为错误处理，当返回部分未定义时，作为错误处理
	ThrowErrorOfNotDefined ActionTypeWhenNotDefined = iota
	//当未定义时，不做任何处理, 当返回整体未定义时，作为错误处理，当返回部分未定义时，忽略
	IgnoreNotDefined
	//当未定义时，自动创建, 当返回整体未定义时，自动创建，当返回部分未定义时，自动创建
	CreateDefWhenNotDefined
)

type DefHandler interface {
	TransformValue(def *openapi3.Schema, value interface{}) (bool, interface{}, error)
	TransformValueVersa(def *openapi3.Schema, value interface{}) (bool, interface{}, error)
	//当返回未定义时，应如何处理
	CreateResponseIfNotDefined() ActionTypeWhenNotDefined
}

type TestResult struct {
	Url                string              `json:"url"`
	Header             http.Header         `json:"Header"`
	Request            interface{}         `json:"request"`
	Response           json.RawMessage     `json:"response"`
	Def                string              `json:"def"`
	Parsed             interface{}         `json:"parsed"`
	X509               *x509.Certificate   `json:"x509"`
	Type               string              `json:"type"`
	Error              interface{}         `json:"error"`
	Code               int                 `json:"code"`
	ResponseName       string              `json:"response_name"`
	MatchedResponseDef *openapi3.MediaType `json:"matched_response_def"`
}

// 对返回结果进行测试
func TestSchemaInResponse(typ string, schema *openapi3.Schema, bts []byte, contentTester DefHandler) (hasChange bool, obj interface{}, err error) {
	if schema.AllOf != nil {
		for _, v := range schema.AllOf {
			hasChange, _, err = TestSchemaInResponse(typ, v.Value, bts, contentTester)
			if err != nil {
				return hasChange, nil, errors.New(errors.ERROR_CALLING_NOT_CORRECT_RESPONSE, err)
			}
		}
		return hasChange, nil, nil
	}

	if schema.Type.Is("object") {
		obj = new(DataResult)
	} else if schema.Type.Is("string") {
		var str string
		obj = &str
	} else if schema.Type.Is("array") {
		var arr []interface{}
		obj = &arr
	} else if schema.Type.Is("number") {
		var num float64
		obj = &num
	} else if schema.Type.Is("integer") {
		var num int64
		obj = &num
	} else if schema.Type.Is("boolean") {
		var b bool
		obj = &b
	}

	switch typ {
	case "application/json":
		err = json.Unmarshal(bts, obj) //DataResult实际是将json转成了map

	case "application/xml":
		err = xml.Unmarshal(bts, obj)
	}

	if err != nil {
		return
	}

	if schema.Type.Is("object") {
		var updated openapi3.Schemas
		hasChange, updated, err = checkForObject(obj.(*DataResult).Values, schema.Properties, contentTester)
		if hasChange {
			schema.Properties = updated
		}
	}

	return
}

func checkForObject(mp map[string]interface{}, schemas openapi3.Schemas, contentTester DefHandler) (bool, openapi3.Schemas, error) {
	var updated = false
	var updatedSchemas = schemas
	for k, v := range mp {
		schema, ok := schemas[k]
		if !ok {
			switch contentTester.CreateResponseIfNotDefined() {
			case CreateDefWhenNotDefined:

				var typ string
				switch v.(type) {
				case string:
					typ = "string"
				case float64:
					typ = "number"
				case int64:
					typ = "integer"
				case bool:
					typ = "boolean"
				case map[string]interface{}:
					typ = "object"
				case []interface{}:
					typ = "array"
				}
				if updatedSchemas == nil {
					updatedSchemas = make(openapi3.Schemas)
				}

				schema = &openapi3.SchemaRef{
					Value: &openapi3.Schema{
						Type: &openapi3.Types{typ},
					},
				}
				updatedSchemas[k] = schema
			case IgnoreNotDefined:
				continue
			default:
				return false, updatedSchemas, errors.New(errors.ERROR_CALLING_NOT_CORRECT_RESPONSE, fmt.Sprintf("参数（%s）返回了结构中未定义的值", k))
			}
		}

		if schema.Value.Type.Is("object") {
			mv, ok := v.(map[string]interface{})
			if !ok {
				return false, updatedSchemas, errors.New(errors.ERROR_CALLING_NOT_CORRECT_RESPONSE, fmt.Sprintf("参数（%s）返回结构不符合定义，需要%s,实际是%T", k, schema.Value.Type, v))
			}
			updatedSingle, result, err := checkForObject(mv, schema.Value.Properties, contentTester)
			if err != nil {
				return updated, updatedSchemas, err
			} else {
				updated = updated || updatedSingle
				if updatedSingle {
					schema.Value.Properties = result
				}
			}
		} else if contentTester != nil {
			//非Object的普通字段
			//TODO 先假设所有字段都提前配置好，不存在没有配置的情况
			updatedSingle, newV, err := contentTester.TransformValue(schema.Value, v)
			if err != nil {
				return updatedSingle, updatedSchemas, err
			} else {
				mp[k] = newV
				updated = updated || updatedSingle
			}
		}
	}
	for k, v := range schemas {
		if _, ok := mp[k]; !ok && isRequired(v.Value.Required, k) {
			return false, updatedSchemas, errors.New(errors.ERROR_CALLING_NOT_CORRECT_RESPONSE, fmt.Sprintf("没有找到需要的参数（%s）", k))
		}
	}
	return updated, updatedSchemas, nil
}

func isRequired(required []string, key string) bool {
	for _, v := range required {
		if v == key {
			return true
		}
	}
	return false
}

func generatePropertiesForValues(values url.Values) (schema *openapi3.Schema, mp map[string]interface{}) {
	if values == nil {
		return
	}
	mp = make(map[string]interface{})
	schema = &openapi3.Schema{
		Type: &openapi3.Types{"object"},
	}
	schema.Properties = make(openapi3.Schemas)
	var typ string
	for k, v := range values {
		if len(v) > 0 {
			mp[k] = v[0]
			if !strings.Contains(v[0], ".") && len(v[0]) > 6 {
				//对于长度大于6的字符串，不考虑是数字或者浮点数，直接当做字符串，避免误判
				typ = "string"
			} else if iV, err := strconv.ParseInt(v[0], 10, 64); err == nil {
				mp[k] = iV
				typ = "integer"
			} else if fV, err := strconv.ParseFloat(v[0], 64); err == nil {
				mp[k] = fV
				typ = "number"
			} else if bV, err := strconv.ParseBool(v[0]); err == nil {
				mp[k] = bV
				typ = "boolean"
			} else {
				typ = "string"
			}
			schema.Properties[k] = &openapi3.SchemaRef{
				Value: &openapi3.Schema{
					Type: &openapi3.Types{typ},
				},
			}
		}
	}
	return
}

func generatePropertiesForJsonObject(bts interface{}) (schema *openapi3.Schema, mp map[string]interface{}) {
	var err error
	if bts == nil {
		return
	}
	switch bts.(type) {
	case []byte:
		err = json.Unmarshal(bts.([]byte), &mp)
	case map[string]interface{}:
		mp = bts.(map[string]interface{})
	}
	schema = &openapi3.Schema{
		Type: &openapi3.Types{"object"},
	}
	if err == nil {
		schema.Properties = make(openapi3.Schemas)
		for k, v := range mp {
			var typ string
			var elem interface{}
			switch tv := v.(type) {
			case string:
				typ = "string"
			case float64:
				typ = "number"
			case int64:
				typ = "integer"
			case bool:
				typ = "boolean"
			case map[string]interface{}:
				typ = "object"
				elem = tv
			case []interface{}:
				typ = "array"
				if len(tv) > 0 {
					elem = tv[0]
				}
			}
			var child *openapi3.Schema
			if typ == "object" {
				child, _ = generatePropertiesForJsonObject(elem)
			} else if typ == "array" {
				child = &openapi3.Schema{
					Type: &openapi3.Types{typ},
				}
				children, _ := generatePropertiesForJsonObject(elem)
				child.Items = &openapi3.SchemaRef{
					Value: children,
				}
			} else {
				child = &openapi3.Schema{
					Type: &openapi3.Types{typ},
				}
			}
			schema.Properties[k] = &openapi3.SchemaRef{
				Value: child,
			}
		}
	}
	return
}
